#include "VideoStreamIPC.hpp"
#include "Client_types.h"
#include <boost/intrusive/intrusive_fwd.hpp>
#include <cstdint>
#include <cstring>
#include <fcntl.h>
#include <nlohmann/json.hpp>
#include <string>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>

VideoStreamIPC::VideoStreamIPC() {}

VideoStreamIPC::~VideoStreamIPC() {
  transport->close();
  for (auto &i : shareMemList) {
    close(i.sharefd);
    sem_destroy(i.sem);
    munmap(i.sharemem, i.memsize);
  }
}

void VideoStreamIPC::Boot(const std::string &host, long port) {
  socket = std::make_shared<apache::thrift::transport::TSocket>(host, port);
  transport =
      std::make_shared<apache::thrift::transport::TBufferedTransport>(socket);
  protocol =
      std::make_shared<apache::thrift::protocol::TBinaryProtocol>(transport);
  IPCClient.reset(new Thrift::NodeClientClient(protocol));

  transport->open();

  RefreshList();
}

void VideoStreamIPC::RefreshList() {
  std::set<std::string> devicelist;
  IPCClient->GetAllDevice(devicelist);
  for (auto &i : devicelist) {
    std::string json;
    IPCClient->GetShareMemInfo(json, i);
    nlohmann::json shareInf = nlohmann::json::parse(json);
    ShareMem mem;
    mem.name = i;
    mem.sem = sem_open(shareInf["SemName"].get<std::string>().c_str(), O_EXCL);
    if (mem.sem == nullptr)
      throw std::runtime_error(std::format(
          "Can't get sem {}", shareInf["MemName"].get<std::string>().c_str()));
    mem.sharefd = shm_open(shareInf["MemName"].get<std::string>().c_str(),
                           O_RDONLY | O_EXCL, S_IRUSR);
    if (mem.sharefd < 0)
      throw std::runtime_error(
          std::format("Can't open sharemem {}",
                      shareInf["MemName"].get<std::string>().c_str()));
    struct stat buf;
    if (fstat(mem.sharefd, &buf))
      throw std::runtime_error(
          std::format("Can't get sharemem inf {}",
                      shareInf["MemName"].get<std::string>().c_str()));
    mem.memsize = buf.st_size;
    mem.sharemem = (unsigned char *)mmap(nullptr, buf.st_size, PROT_READ,
                                         MAP_SHARED, mem.sharefd, 0);
    if (mem.sharemem == MAP_FAILED)
      throw std::runtime_error(std::format(
          "Can't mmap {}", shareInf["MemName"].get<std::string>().c_str()));
    shareMemList.push_back(mem);
  }
}

std::vector<std::string> VideoStreamIPC::GetRealtimeImageList() {
  std::vector<std::string> res;
  res.reserve(shareMemList.size());
  for (auto &i : shareMemList)
    res.push_back(i.name);
  return res;
}

void VideoStreamIPC::GetRealtimeImage(int index, unsigned char *data,
                                      long size) {
  if (sem_wait(shareMemList[index].sem))
    throw std::runtime_error("Can't lock memory");
  memcpy(data, shareMemList[index].sharemem + sizeof(int64_t), size);
  if (sem_post(shareMemList[index].sem))
    throw std::runtime_error("Can't lock memory");
}

std::tuple<int, int, Thrift::ImageFormat::type>
VideoStreamIPC::GetPreviewImageInf(const std::string &uuid) {
  std::string info;
  IPCClient->GetPreviewImageInfo(info, uuid);
  nlohmann::json j = nlohmann::json::parse(info);
  return std::make_tuple<int, int, Thrift::ImageFormat::type>(
      j["Width"], j["Height"], [&]() -> Thrift::ImageFormat::type {
        int format = j["Format"];
        return (Thrift::ImageFormat::type)format;
      }());
}
